package com.qiangzi.workflow.engine.service.impl;

import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;

import com.google.common.collect.Sets;
import com.qiangzi.workflow.engine.bpmn.base.BaseElement;
import com.qiangzi.workflow.engine.bpmn.base.Edge;
import com.qiangzi.workflow.engine.bpmn.base.Node;
import com.qiangzi.workflow.engine.bpmn.base.ProcessDefinition;
import com.qiangzi.workflow.engine.bpmn.event.EndEvent;
import com.qiangzi.workflow.engine.bpmn.event.StartEvent;
import com.qiangzi.workflow.engine.bpmn.flow.SequenceFlow;
import com.qiangzi.workflow.engine.bpmn.gateway.ExclusiveGateway;
import com.qiangzi.workflow.engine.bpmn.gateway.InclusiveGateway;
import com.qiangzi.workflow.engine.bpmn.gateway.ParallelGateway;
import com.qiangzi.workflow.engine.bpmn.process.SubProcess;
import com.qiangzi.workflow.engine.bpmn.task.ServiceTask;
import com.qiangzi.workflow.engine.helper.CloseHelper;
import com.qiangzi.workflow.engine.service.XmlParserService;

public class Dom4JXmlParserService implements XmlParserService {

	public static final Logger LOGGER = LoggerFactory.getLogger(Dom4JXmlParserService.class);

	@Override
	public ProcessDefinition parse(Resource resource) throws Exception {

		InputStream inputStream = null;

		try {
			Assert.notNull(resource, "xml resource is null");
			inputStream = resource.getInputStream();
			Assert.notNull(inputStream, "xml input stream is null");
			Document document = new SAXReader().read(inputStream);
			LOGGER.debug("document is {}", document);
			// 解析这个XML文档
			return parseDocument(document);
		} catch (Exception e) {
			LOGGER.error(e.toString());
			throw e;
		} finally {
			// 必要时关闭资源
			CloseHelper.close(inputStream);
		}

	}

	@SuppressWarnings({ "unchecked" })
	private ProcessDefinition parseDocument(Document document) throws Exception {

		Assert.notNull(document, "document is null");

		// 获取definitions元素
		Element root = document.getRootElement();
		Assert.isTrue(null != root && null != root.getName() && root.getName().equals("definitions"),
				"root element must be definitions");
		Element definitions = root;

		// 获取process元素
		List<Element> processList = definitions.elements("process");
		Assert.isTrue(null != processList && 1 == processList.size(), "process list size must be 1");
		Element process = (Element) processList.get(0);
		// 提取这个元素的ID和name,这里不再引入其它类,直接提取
		String processId = BaseElement.parseId(process);
		String processName = BaseElement.parseName(process);
		LOGGER.debug("process is {} ,id {} , name {}", process, processId, processName);

		// 开始解析process的子元素,分门别类
		List<Element> children = process.elements();
		Assert.isTrue(null != children && children.size() >= 1, "child element of process not valid");

		// 开始提取每个子元素
		Map<String, Node> nodeMap = new HashMap<String, Node>(64);
		Map<String, Edge> edgeMap = new HashMap<String, Edge>(64);
		//
		Node previousNode = null;
		Edge previousEdge = null;

		for (Element child : children) {
			String name = child.getName();
			Assert.isTrue(null != name && name.length() > 0, "name of element is not valid");
			switch (name) {

			case "startEvent":
				StartEvent startEvent = new StartEvent();
				startEvent.parse(child);
				previousNode = nodeMap.put(startEvent.getId(), startEvent);
				Assert.isTrue(null == previousNode, "duplicate id -> " + startEvent.getId());
				LOGGER.info("{} parsed", startEvent);
				break;

			case "endEvent":
				EndEvent endEvent = new EndEvent();
				endEvent.parse(child);
				previousNode = nodeMap.put(endEvent.getId(), endEvent);
				Assert.isTrue(null == previousNode, "duplicate id ->" + endEvent.getId());
				LOGGER.info("{} parsed", endEvent);
				break;

			case "sequenceFlow":
				SequenceFlow sequenceFlow = new SequenceFlow();
				sequenceFlow.parse(child);
				previousEdge = edgeMap.put(sequenceFlow.getId(), sequenceFlow);
				Assert.isTrue(null == previousEdge, "duplicate id -> " + sequenceFlow.getId());
				LOGGER.info("{} parsed", sequenceFlow);
				break;

			case "serviceTask":
				ServiceTask serviceTask = new ServiceTask();
				serviceTask.parse(child);
				previousNode = nodeMap.put(serviceTask.getId(), serviceTask);
				Assert.isTrue(null == previousNode, "duplicate id -> " + serviceTask.getId());
				LOGGER.info("{} parsed", serviceTask);
				break;

			case "exclusiveGateway":
				ExclusiveGateway exclusiveGateway = new ExclusiveGateway();
				exclusiveGateway.parse(child);
				previousNode = nodeMap.put(exclusiveGateway.getId(), exclusiveGateway);
				Assert.isTrue(null == previousNode, "duplicate id -> " + exclusiveGateway.getId());
				LOGGER.info("{} parsed", exclusiveGateway);
				break;

			case "parallelGateway":
				ParallelGateway parllelGateway = new ParallelGateway();
				parllelGateway.parse(child);
				previousNode = nodeMap.put(parllelGateway.getId(), parllelGateway);
				Assert.isTrue(null == previousNode, "duplicate id -> " + parllelGateway.getId());
				LOGGER.info("{} parsed", parllelGateway);
				break;

			case "inclusiveGateway":
				InclusiveGateway inclusiveGateway = new InclusiveGateway();
				inclusiveGateway.parse(child);
				previousNode = nodeMap.put(inclusiveGateway.getId(), inclusiveGateway);
				Assert.isTrue(null == previousNode, "duplicate id -> " + inclusiveGateway.getId());
				LOGGER.info("{} parsed", inclusiveGateway);
				break;

			// 2019-06-25增加对子流程的解析
			case "subProcess":
				SubProcess subProcess = new SubProcess();
				subProcess.parse(child);
				previousNode = nodeMap.put(subProcess.getId(), subProcess);
				Assert.isTrue(null == previousNode, "duplicate id -> " + subProcess.getId());
				LOGGER.info("{} parsed", subProcess);
				break;

			default:
				String errorMsg = "not valid element [" + name + "]";
				LOGGER.error(errorMsg);
				throw new Exception(errorMsg);
			}

		}

		// 验证有效性
		validate0(nodeMap, edgeMap);

		// 真正连接点+边,并做有效性验证
		ProcessDefinition topology = new ProcessDefinition(nodeMap, edgeMap, processId, processName);

		LOGGER.info("build temporary instance ok");
		return topology;

	}

	private void validate0(Map<String, Node> nodeMap, Map<String, Edge> edgeMap) {

		Assert.isTrue(null != nodeMap && nodeMap.size() > 0, "node map must size >=1");
		Assert.isTrue(null != edgeMap && edgeMap.size() > 0, "edge Map must size >=1");

		Set<String> nodeKeySet = nodeMap.keySet();
		Set<String> edgeKeySet = edgeMap.keySet();

		// 1)2者的ID不应该有重复的
		// 交集
		Set<String> sameKeySet = Sets.intersection(nodeKeySet, edgeKeySet);
		Assert.isTrue(null == sameKeySet || 0 == sameKeySet.size(), "duplicate id -> " + sameKeySet);

		// 2)其它的校验可以放在buildTopology环节实时发现问题
		LOGGER.info("basic validate ok");

	}

}
